﻿using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using LFSRecord2.Events;
using LFSRecord2.Model;
using LFSRecord2.Model.UserActions;
using LFSRecord2.Structures;
using LFSRecord2.View.Converters;
using LFSRecord2.View.Dialogs;

namespace LFSRecord2.View.ViewportStructures
{
    /// <summary>
    /// Interaction logic for CompLayerPathDrawing.xaml
    /// </summary>
    public partial class CompLayerKeyFrameDrawing : Canvas
    {
        public static readonly DependencyProperty PositionProperty = DependencyProperty.Register(
            "Position",
            typeof(Point3D),
            typeof(CompLayerKeyFrameDrawing),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnPositionPropertyChanged))
        );
        public Point3D Position
        {
            get { return (Point3D)GetValue(PositionProperty); }
            set { SetValue(PositionProperty, value); }
        }
        private static void OnPositionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //Debug.WriteLine("Keyframe redraw");
            ((CompLayerKeyFrameDrawing)d).redraw();
        }

        public static readonly DependencyProperty BezierControlInProperty = DependencyProperty.Register(
            "BezierControlIn",
            typeof(Point3D),
            typeof(CompLayerKeyFrameDrawing),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnBezierControlInPropertyChanged))
        );
        public Point3D BezierControlIn
        {
            get { return (Point3D)GetValue(BezierControlInProperty); }
            set { SetValue(BezierControlInProperty, value); }
        }
        private static void OnBezierControlInPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((CompLayerKeyFrameDrawing)d).redraw();
        }

        public static readonly DependencyProperty BezierControlOutProperty = DependencyProperty.Register(
            "BezierControlOut",
            typeof(Point3D),
            typeof(CompLayerKeyFrameDrawing),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnBezierControlOutPropertyChanged))
        );
        public Point3D BezierControlOut
        {
            get { return (Point3D)GetValue(BezierControlOutProperty); }
            set { SetValue(BezierControlOutProperty, value); }
        }
        private static void OnBezierControlOutPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((CompLayerKeyFrameDrawing)d).redraw();
        }

        public static readonly DependencyProperty InterpolationTypeInProperty = DependencyProperty.Register(
            "InterpolationTypeIn",
            typeof(KeyFrameInterpolationTypes),
            typeof(CompLayerKeyFrameDrawing),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnInterpolationTypeInPropertyChanged))
        );
        public KeyFrameInterpolationTypes InterpolationTypeIn
        {
            get { return (KeyFrameInterpolationTypes)GetValue(InterpolationTypeInProperty); }
            set { SetValue(InterpolationTypeInProperty, value); }
        }
        private static void OnInterpolationTypeInPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((CompLayerKeyFrameDrawing)d).redraw();
        }

        public static readonly DependencyProperty InterpolationTypeOutProperty = DependencyProperty.Register(
            "InterpolationTypeOut",
            typeof(KeyFrameInterpolationTypes),
            typeof(CompLayerKeyFrameDrawing),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnInterpolationTypeOutPropertyChanged))
        );
        public KeyFrameInterpolationTypes InterpolationTypeOut
        {
            get { return (KeyFrameInterpolationTypes)GetValue(InterpolationTypeOutProperty); }
            set { SetValue(InterpolationTypeOutProperty, value); }
        }
        private static void OnInterpolationTypeOutPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((CompLayerKeyFrameDrawing)d).redraw();
        }

        public static readonly DependencyProperty KeyOptionsProperty = DependencyProperty.Register(
            "KeyOptions",
            typeof(CustomKeyFrameOptions),
            typeof(CompLayerKeyFrameDrawing),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnKeyOptionsPropertyChanged))
        );
        public CustomKeyFrameOptions KeyOptions
        {
            get { return (CustomKeyFrameOptions)GetValue(KeyOptionsProperty); }
            set { SetValue(KeyOptionsProperty, value); }
        }
        private static void OnKeyOptionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //Debug.WriteLine("Key options changed - " + e.OldValue + " - " + e.NewValue);
            //if ((((CustomKeyFrameOptions)e.OldValue ^ (CustomKeyFrameOptions)e.NewValue) & CustomKeyFrameOptions.AutoBezier) > 0)
            if (e.OldValue != e.NewValue)
            {
                ((CompLayerKeyFrameDrawing)d).redraw();
                ((CompLayerKeyFrameDrawing)d).redrawBezierFills();
            }
        }

        public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(
            "IsSelected",
            typeof(bool),
            typeof(CompLayerKeyFrameDrawing),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsSelectedPropertyChanged))
        );
        public bool IsSelected
        {
            get { return (bool)GetValue(IsSelectedProperty); }
            set { SetValue(IsSelectedProperty, value); }
        }
        private static void OnIsSelectedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((CompLayerKeyFrameDrawing)d).redraw();
        }

        TranslateTransform _positionTransform = new TranslateTransform();
        public double Scale = 1;
        public ViewportViews CurrentView = ViewportViews.Top;

        public CompLayerKeyFrameDrawing()
        {
            Scale = LfsRecordController.ViewportScale;
            CurrentView = LfsRecordController.ViewportView;
            InitializeComponent();

            if (Scale != 1)
                rescaleObjects();
            RenderTransform = _positionTransform;
        }

        private void CompLayerKeyFrameDrawing_Loaded(object sender, RoutedEventArgs e)
        {
            CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)DataContext;

            // Set binding
            Binding myBinding = new Binding("Value");
            myBinding.Source = kf;
            SetBinding(CompLayerKeyFrameDrawing.PositionProperty, myBinding);

            myBinding = new Binding("BezierControlIn");
            myBinding.Source = kf;
            SetBinding(CompLayerKeyFrameDrawing.BezierControlInProperty, myBinding);

            myBinding = new Binding("BezierControlOut");
            myBinding.Source = kf;
            SetBinding(CompLayerKeyFrameDrawing.BezierControlOutProperty, myBinding);

            myBinding = new Binding("InterpolationTypeIn");
            myBinding.Source = kf;
            SetBinding(CompLayerKeyFrameDrawing.InterpolationTypeInProperty, myBinding);

            myBinding = new Binding("InterpolationTypeOut");
            myBinding.Source = kf;
            SetBinding(CompLayerKeyFrameDrawing.InterpolationTypeOutProperty, myBinding);

            myBinding = new Binding("Options");
            myBinding.Source = kf;
            SetBinding(CompLayerKeyFrameDrawing.KeyOptionsProperty, myBinding);

            myBinding = new Binding("IsSelected");
            myBinding.Source = kf;
            SetBinding(CompLayerKeyFrameDrawing.IsSelectedProperty, myBinding);

            redrawBezierFills();

            //setupContextMenu();
            //Debug.WriteLine("Loaded KeyFrameDrawingObject");
        }

        private void CompLayerKeyFrameDrawing_Unloaded(object sender, RoutedEventArgs e)
        {
            //Debug.WriteLine("Unloaded KeyFrameDrawingObject");
            //Debug.WriteLine("DataContext type : " + DataContext.GetType());
            if (DataContext.GetType() != typeof(CustomPoint3DKeyFrame))
            {
                //Debug.WriteLine("Disposing keyframe");
                DataContext = null;
            }
            BindingOperations.ClearBinding(this, CompLayerKeyFrameDrawing.PositionProperty);
            BindingOperations.ClearBinding(this, CompLayerKeyFrameDrawing.BezierControlInProperty);
            BindingOperations.ClearBinding(this, CompLayerKeyFrameDrawing.BezierControlOutProperty);
            BindingOperations.ClearBinding(this, CompLayerKeyFrameDrawing.InterpolationTypeInProperty);
            BindingOperations.ClearBinding(this, CompLayerKeyFrameDrawing.InterpolationTypeOutProperty);
            BindingOperations.ClearBinding(this, CompLayerKeyFrameDrawing.KeyOptionsProperty);
            BindingOperations.ClearBinding(this, CompLayerKeyFrameDrawing.IsSelectedProperty);
        }

        public void ViewportScaleChangedHandler(double scale)
        {
            Scale = scale;
            rescaleObjects();
            redraw();
        }

        public void ViewportViewChangedHandler(ViewportViews newView)
        {
            CurrentView = newView;

            if (CurrentView != ViewportViews.ThreeD)
            {
                redraw();
            }
        }

        public void redraw()
        {
            if (DataContext == null)
                return;

            CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)DataContext;
            Matrix3D m = Matrix3D.Multiply(kf.ParentLayer.GetParentedTransformMatrixAtFrame(LfsRecordController.FrameIndex, false), LfsRecordController.ViewTransformMatrix);
            Point3D p = m.Transform(kf.Value);

            _positionTransform.X = p.X;
            _positionTransform.Y = p.Y;
            //RenderTransform = _positionTransform;

            //Debug.WriteLine("redrawing keyframe - " + kf.FrameNum);

            // Draw bezier stuff?
            if (kf.IsSelected)
            {
                BezierHandleIn.Visibility =
                    ((kf.Options & CustomKeyFrameOptions.FirstKey) > 0 || kf.InterpolationTypeIn == KeyFrameInterpolationTypes.Linear) ?
                    Visibility.Hidden :
                    Visibility.Visible;
                BezierHandleOut.Visibility =
                    ((kf.Options & CustomKeyFrameOptions.LastKey) > 0 || kf.InterpolationTypeOut == KeyFrameInterpolationTypes.Linear || (kf.Options & CustomKeyFrameOptions.Hold) > 0) ?
                    Visibility.Hidden :
                    Visibility.Visible;

                // Remove the translation from the matrix for our bezier items (that are relative to keyframe rectangle)
                m.OffsetX = 0;
                m.OffsetY = 0;
                m.OffsetZ = 0;
                Point3D bezierIn = m.Transform(BezierControlIn);
                Point3D bezierOut = m.Transform(BezierControlOut);
                if (double.IsNaN(bezierIn.X)) bezierIn.X = 0;
                if (double.IsNaN(bezierIn.Y)) bezierIn.Y = 0;
                if (double.IsNaN(bezierIn.Z)) bezierIn.Z = 0;
                if (double.IsNaN(bezierOut.X)) bezierOut.X = 0;
                if (double.IsNaN(bezierOut.Y)) bezierOut.Y = 0;
                if (double.IsNaN(bezierOut.Z)) bezierOut.Z = 0;

                TransformGroup trGroup = new TransformGroup();
                trGroup.Children.Add(new ScaleTransform(1 / Scale, 1 / Scale));
                trGroup.Children.Add(new TranslateTransform(bezierIn.X, bezierIn.Y));
                BezierHandleIn.RenderTransform = trGroup;

                trGroup = new TransformGroup();
                trGroup.Children.Add(new ScaleTransform(1 / Scale, 1 / Scale));
                trGroup.Children.Add(new TranslateTransform(bezierOut.X, bezierOut.Y));
                BezierHandleOut.RenderTransform = trGroup;

                if ((KeyOptions & CustomKeyFrameOptions.AutoBezier) == 0)
                {
                    BezierInConnectorLine.Visibility =
                        ((kf.Options & CustomKeyFrameOptions.FirstKey) > 0 || kf.InterpolationTypeIn == KeyFrameInterpolationTypes.Linear) ?
                        Visibility.Hidden :
                        Visibility.Visible;
                    BezierInConnectorLine.StrokeThickness = 1 / Scale;
                    BezierInConnectorLine.X1 = 0;
                    BezierInConnectorLine.Y1 = 0;
                    BezierInConnectorLine.X2 = bezierIn.X;
                    BezierInConnectorLine.Y2 = bezierIn.Y;

                    BezierOutConnectorLine.Visibility =
                        ((kf.Options & CustomKeyFrameOptions.LastKey) > 0 || kf.InterpolationTypeOut == KeyFrameInterpolationTypes.Linear || (kf.Options & CustomKeyFrameOptions.Hold) > 0) ?
                        Visibility.Hidden :
                        Visibility.Visible;
                    BezierOutConnectorLine.StrokeThickness = 1 / Scale;
                    BezierOutConnectorLine.X1 = 0;
                    BezierOutConnectorLine.Y1 = 0;
                    BezierOutConnectorLine.X2 = bezierOut.X;
                    BezierOutConnectorLine.Y2 = bezierOut.Y;
                }
                else
                {
                    BezierInConnectorLine.Visibility = Visibility.Hidden;
                    BezierOutConnectorLine.Visibility = Visibility.Hidden;
                }
            }
            else
            {
                BezierHandleIn.Visibility = Visibility.Hidden;
                BezierHandleOut.Visibility = Visibility.Hidden;
                BezierInConnectorLine.Visibility = Visibility.Hidden;
                BezierOutConnectorLine.Visibility = Visibility.Hidden;
            }
        }

        private void redrawBezierFills()
        {
            if ((KeyOptions & CustomKeyFrameOptions.AutoBezier) > 0)
            {
                BezierHandleIn.Fill = Brushes.Transparent;
                BezierHandleOut.Fill = Brushes.Transparent;
            }
            else
            {
                BezierHandleIn.Fill = Brushes.White;
                BezierHandleOut.Fill = Brushes.White;
            }
        }

        private void rescaleObjects()
        {
            KeyRectangle.RenderTransform = new ScaleTransform(1 / Scale, 1 / Scale);
        }

        bool _objectHasMoved = false;
        Vector3D _objectMoveVec;
        Point _prevPoint;
        private void KeyRectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            //Debug.WriteLine("KeyFrame Rectangle clicked");

            e.Handled = true;
            this.IsHitTestVisible = false;
            CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)DataContext;

            Application.Current.MainWindow.CaptureMouse();
            Application.Current.MainWindow.MouseUp += KeyFrame_MouseUp;
            Application.Current.MainWindow.MouseMove += KeyFrame_MouseMove;

            // Must we adjust the selecting?
            if (!kf.IsSelected)
            {
                if (!Keyboard.IsKeyDown(Key.LeftShift))
                    LfsRecordController.project.DeselectAllKeyFrames();
                kf.IsSelected = true;
            }
            else
            {
                if (Keyboard.IsKeyDown(Key.LeftShift))
                    kf.IsSelected = false;
            }

            // Prepare for mouse move capturing
            _prevPoint = e.GetPosition(null);
            _objectHasMoved = false;
            _objectMoveVec = new Vector3D();
        }

        private void KeyFrame_MouseUp(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
            this.IsHitTestVisible = true;
            CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)DataContext;

            Application.Current.MainWindow.ReleaseMouseCapture();
            Application.Current.MainWindow.MouseMove -= KeyFrame_MouseMove;
            Application.Current.MainWindow.MouseUp -= KeyFrame_MouseUp;

            if (_objectHasMoved)
            {
                // Record user action
                //Debug.WriteLine("Recording p3 vector : {0}, {1}, {2}", _objectMoveVec.X, _objectMoveVec.Y, _objectMoveVec.Z);
                LfsRecordController.project.SubmitUserActionSelectedKeyFramePoint3DValueChange(new Vector3D(-_objectMoveVec.X, -_objectMoveVec.Y, -_objectMoveVec.Z));
                LfsRecordController.CreateUndoLevel();
            }
            _objectHasMoved = false;
        }

        private void KeyFrame_MouseMove(object sender, MouseEventArgs e)
        {
            Point mPos = e.GetPosition(null);
            Vector3D diff3D = new Vector3D(_prevPoint.X - mPos.X, _prevPoint.Y - mPos.Y, 0);
            _prevPoint = mPos;

            if (diff3D.X == 0 && diff3D.Y == 0)
                return;

            CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)DataContext;

            Matrix3D invMat = Matrix3D.Multiply(kf.ParentLayer.GetParentedTransformMatrixAtFrame(LfsRecordController.FrameIndex, false), LfsRecordController.ViewTransformMatrix);
            invMat.OffsetX = 0;
            invMat.OffsetY = 0;
            invMat.OffsetZ = 0; 
            invMat.Invert();
            diff3D = invMat.Transform(diff3D);

            _objectHasMoved = true;
            _objectMoveVec += diff3D;

            //Debug.WriteLine("Moving by : {0}, {1}", diff.X, diff.Y);
            LfsRecordController.project.TranslateSelectedKeyFrames(diff3D.X / Scale, diff3D.Y / Scale, diff3D.Z / Scale);
        }

        bool _bezierHandleIn;
        Point3D _bInOrig;
        Point3D _bOutOrig;

        private void BezierHandleIn_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)DataContext;
            e.Handled = true;
            this.IsHitTestVisible = false;
            _bezierHandleIn = true;

            // Start mouse move capturing
            _prevPoint = e.GetPosition(null);
            _objectHasMoved = false;
            _bInOrig = kf.BezierControlIn;
            _bOutOrig = kf.BezierControlOut;

            Application.Current.MainWindow.CaptureMouse();
            Application.Current.MainWindow.MouseMove += BezierHandle_MouseMove;
            Application.Current.MainWindow.MouseUp += BezierHandle_MouseUp;
        }

        private void BezierHandleOut_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)DataContext;
            e.Handled = true;
            this.IsHitTestVisible = false;
            _bezierHandleIn = false;

            // Start mouse move capturing
            _prevPoint = e.GetPosition(null);
            _objectHasMoved = false;
            _bInOrig = kf.BezierControlIn;
            _bOutOrig = kf.BezierControlOut;

            Application.Current.MainWindow.CaptureMouse();
            Application.Current.MainWindow.MouseMove += BezierHandle_MouseMove;
            Application.Current.MainWindow.MouseUp += BezierHandle_MouseUp;
        }

        private void BezierHandle_MouseUp(object sender, MouseButtonEventArgs e)
        {
            CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)DataContext;
            e.Handled = true;
            this.IsHitTestVisible = true;

            // End mouse move capturing
            //Debug.WriteLine("Keyframe mouse UP");
            Application.Current.MainWindow.ReleaseMouseCapture();
            Application.Current.MainWindow.MouseMove -= BezierHandle_MouseMove;
            Application.Current.MainWindow.MouseUp -= BezierHandle_MouseUp;

            if (_objectHasMoved)
            {
                LfsRecordController.RecordUserAction(new UserActionKeyFrameBezierChanged()
                    {
                        LayerIndex = kf.ParentLayer.LayerIndex,
                        PropertyIndex = kf.ParentProperty.PropertyIndex,
                        KeyFrameIndex = kf.KeyFrameIndex,
                        BezierControlInOffset = Point3D.Subtract(kf.BezierControlIn, _bInOrig),
                        BezierControlOutOffset = Point3D.Subtract(kf.BezierControlOut, _bOutOrig)
                    });
                LfsRecordController.CreateUndoLevel();
            }
            _objectHasMoved = false;
        }

        private void BezierHandle_MouseMove(object sender, MouseEventArgs e)
        {
            e.Handled = true;
            
            //Debug.WriteLine("Bezier handle moved");
            Point mPos = e.GetPosition(null);
            Vector3D diff3D = new Vector3D(_prevPoint.X - mPos.X, _prevPoint.Y - mPos.Y, 0);
            _prevPoint = mPos;

            if (diff3D.X == 0 && diff3D.Y == 0)
                return;
            _objectHasMoved = true;

            CustomPoint3DKeyFrame kf = (CustomPoint3DKeyFrame)DataContext;

            // Rotate the input according to inverted matrix, compensating for (view) rotations
            Matrix3D mat = Matrix3D.Multiply(kf.ParentLayer.GetParentedTransformMatrixAtFrame(LfsRecordController.FrameIndex, false), LfsRecordController.ViewTransformMatrix);
            mat.OffsetX = 0;
            mat.OffsetY = 0;
            mat.OffsetZ = 0;
            //mat.Invert();
            //diff3D = mat.Transform(diff3D);
            diff3D /= Scale;

            // Update the keyframe bezier values
            Vector bVecOrig, bVecOpposite;
            Point3D bRotated, bOppositeRotated;
            double origLen, origLenScale, vecAngleOpposite, oppositeOrigLen;

            if (_bezierHandleIn)
            {
                // Rotate the bezier control, so that we can look at it from 'the top' (y is up, x is to the right)
                bRotated = mat.Transform(kf.BezierControlIn);
                bVecOrig = new Vector(bRotated.X, bRotated.Y);
                origLen = bVecOrig.Length;

                bRotated.X -= diff3D.X;
                bRotated.Y -= diff3D.Y;
                bRotated.Z -= diff3D.Z;

                bOppositeRotated = mat.Transform(kf.BezierControlOut);
                bVecOpposite = new Vector(bOppositeRotated.X, bOppositeRotated.Y);
                mat.Invert();

                if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl))
                {
                    // Calculate angle between the original control and opposite control, around Z axis
                    vecAngleOpposite = Math.Atan2(bVecOpposite.Y, bVecOpposite.X) - Math.Atan2(bVecOrig.Y, bVecOrig.X);
                    
                    bVecOrig = new Vector(bRotated.X, bRotated.Y);
                    origLenScale = bVecOrig.Length / origLen;
                    oppositeOrigLen = (bVecOpposite.X != 0 || bVecOpposite.Y != 0) ? bVecOpposite.Length * origLenScale : 0;

                    if (bVecOrig.X != 0 || bVecOrig.Y != 0)
                        bVecOrig.Normalize();
                    bOppositeRotated = new Point3D(
                            oppositeOrigLen * (bVecOrig.X * Math.Cos(vecAngleOpposite) - bVecOrig.Y * Math.Sin(vecAngleOpposite)),
                            oppositeOrigLen * (bVecOrig.X * Math.Sin(vecAngleOpposite) + bVecOrig.Y * Math.Cos(vecAngleOpposite)),
                            bOppositeRotated.Z);

                    // Invert rotation again
                    kf.BezierControlOut = mat.Transform(bOppositeRotated);
                }

                // Invert rotation again
                kf.BezierControlIn = mat.Transform(bRotated);
            }
            else
            {
                // Rotate the bezier control, so that we can look at it from 'the top' (y is up, x is to the right)
                bRotated = mat.Transform(kf.BezierControlOut);
                bVecOrig = new Vector(bRotated.X, bRotated.Y);
                origLen = bVecOrig.Length;

                bRotated.X -= diff3D.X;
                bRotated.Y -= diff3D.Y;
                bRotated.Z -= diff3D.Z;

                bOppositeRotated = mat.Transform(kf.BezierControlIn);
                bVecOpposite = new Vector(bOppositeRotated.X, bOppositeRotated.Y);
                mat.Invert();

                if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl))
                {
                    // Calculate angle between the original control and opposite control, around Z axis
                    vecAngleOpposite = Math.Atan2(bVecOpposite.Y, bVecOpposite.X) - Math.Atan2(bVecOrig.Y, bVecOrig.X);

                    bVecOrig = new Vector(bRotated.X, bRotated.Y);
                    origLenScale = bVecOrig.Length / origLen;
                    oppositeOrigLen = (bVecOpposite.X != 0 || bVecOpposite.Y != 0) ? bVecOpposite.Length * origLenScale : 0;

                    if (bVecOrig.X != 0 || bVecOrig.Y != 0)
                        bVecOrig.Normalize();
                    bOppositeRotated = new Point3D(
                            oppositeOrigLen * (bVecOrig.X * Math.Cos(vecAngleOpposite) - bVecOrig.Y * Math.Sin(vecAngleOpposite)),
                            oppositeOrigLen * (bVecOrig.X * Math.Sin(vecAngleOpposite) + bVecOrig.Y * Math.Cos(vecAngleOpposite)),
                            bOppositeRotated.Z);

                    // Invert rotation again
                    kf.BezierControlIn = mat.Transform(bOppositeRotated);
                }

                // Invert rotation again
                kf.BezierControlOut = mat.Transform(bRotated);
            }

            // Toggle and record user action if auto bezier is on
            if ((kf.Options & CustomKeyFrameOptions.AutoBezier) > 0)
            {
                CustomKeyFrameOptions oldOptions = kf.Options;
                kf.Options &= ~CustomKeyFrameOptions.AutoBezier;
                LfsRecordController.RecordUserAction(new UserActionKeyFrameOptionChanged()
                    {
                        LayerIndex = kf.ParentLayer.LayerIndex,
                        PropertyIndex = kf.ParentProperty.PropertyIndex,
                        KeyFrameIndex = kf.KeyFrameIndex,
                        OldOptions = oldOptions,
                        NewOptions = kf.Options
                    });
            }

            //if (_bezierHandleIn)
            //{
            //    pOrig = kf.BezierControlIn;
            //    bVecOrig = new Vector3D(kf.BezierControlIn.X, kf.BezierControlIn.Y, kf.BezierControlIn.Z);

            //    kf.BezierControlIn = new Point3D(
            //        kf.BezierControlIn.X - diff3D.X,
            //        kf.BezierControlIn.Y - diff3D.Y,
            //        kf.BezierControlIn.Z - diff3D.Z);

            //    if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl))
            //    {
            //        bVecOpposite = new Vector3D(kf.BezierControlOut.X, kf.BezierControlOut.Y, kf.BezierControlOut.Z);

            //        pVec = Point3D.Subtract(kf.BezierControlIn, pOrig);
            //        if (bVecOrig.Length > 0)
            //            pVec *= -bVecOpposite.Length / bVecOrig.Length;

            //        kf.BezierControlOut = new Point3D(
            //            kf.BezierControlOut.X + pVec.X,
            //            kf.BezierControlOut.Y + pVec.Y,
            //            kf.BezierControlOut.Z + pVec.Z
            //            );
            //    }

            //    kf.Options &= ~CustomKeyFrameOptions.AutoBezier;
            //}
            //else
            //{
            //    pOrig = kf.BezierControlOut;
            //    bVecOrig = new Vector3D(kf.BezierControlOut.X, kf.BezierControlOut.Y, kf.BezierControlOut.Z);

            //    kf.BezierControlOut = new Point3D(
            //        kf.BezierControlOut.X - diff3D.X,
            //        kf.BezierControlOut.Y - diff3D.Y,
            //        kf.BezierControlOut.Z - diff3D.Z);

            //    if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl))
            //    {
            //        bVecOpposite = new Vector3D(kf.BezierControlIn.X, kf.BezierControlIn.Y, kf.BezierControlIn.Z);

            //        pVec = Point3D.Subtract(kf.BezierControlOut, pOrig);
            //        if (bVecOrig.Length > 0)
            //            pVec *= -bVecOpposite.Length / bVecOrig.Length;

            //        kf.BezierControlIn = new Point3D(
            //            kf.BezierControlIn.X + pVec.X,
            //            kf.BezierControlIn.Y + pVec.Y,
            //            kf.BezierControlIn.Z + pVec.Z
            //            );
            //    }

            //    kf.Options &= ~CustomKeyFrameOptions.AutoBezier;
            //}

            kf.OnPropertyChanged("BezierControl");
            ServicesFactory.EventService.GetEvent<CompLayerPropertyChangedEvent>().Publish(new CompLayerPropertyChangedEventArgs() { Sender = this, PropertyType = "BezierControl", UpdateCurrentValues = true });
        }

        private void KeyRectangle_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (KeyRectangle.ContextMenu == null)
            {
                setupContextMenu();
                //KeyRectangle.ContextMenu.IsOpen = true;
            }
        }

        private void setupContextMenu()
        {
            //Debug.WriteLine("Setting up other context menu");
            // Create context menu
            MenuItem mi, mi2;
            Separator sep = new Separator();
            Binding myBinding;
            ContextMenu cm = new ContextMenu();
            CustomKeyFrame kf = (CustomKeyFrame)DataContext;

            // EDIT VALUE
            mi = new MenuItem();
            mi.Header = "Edit Value";
            mi.Click += EditValueClicked;
            if (kf.KeyFrameType == CustomKeyFrameTypes.String)
                mi.IsEnabled = false;
            cm.Items.Add(mi);

            // EASING
            mi = new MenuItem();
            mi.Header = "Easing";

            // No ease
            mi2 = new MenuItem();
            mi2.Name = "NoEase";
            mi2.Header = "No Ease";
            //mi2.IsCheckable = true;
            myBinding = new Binding("HasNoEase");
            myBinding.Mode = BindingMode.OneWay;
            myBinding.Source = kf;
            mi2.SetBinding(MenuItem.IsCheckedProperty, myBinding);
            mi2.Command = KeyFrameOption_ClickedCommand;
            mi2.CommandParameter = KeyFramePropertyTypes.NoEase;
            mi.Items.Add(mi2);

            // Ease In
            mi2 = new MenuItem();
            mi2.Name = "EaseIn";
            mi2.Header = "Ease In";
            //mi2.IsCheckable = true;
            myBinding = new Binding("HasEaseIn");
            myBinding.Mode = BindingMode.OneWay;
            myBinding.Source = kf;
            mi2.SetBinding(MenuItem.IsCheckedProperty, myBinding);
            mi2.Command = KeyFrameOption_ClickedCommand;
            mi2.CommandParameter = KeyFramePropertyTypes.EaseIn;
            mi.Items.Add(mi2);

            // Ease Out
            mi2 = new MenuItem();
            mi2.Name = "EaseOut";
            mi2.Header = "Ease Out";
            //mi2.IsCheckable = true;
            myBinding = new Binding("HasEaseOut");
            myBinding.Mode = BindingMode.OneWay;
            myBinding.Source = kf;
            mi2.SetBinding(MenuItem.IsCheckedProperty, myBinding);
            myBinding = new Binding("HasHoldValue");
            myBinding.Mode = BindingMode.OneWay;
            myBinding.Converter = new BoolInvertConverter();
            myBinding.Source = kf;
            mi2.SetBinding(MenuItem.IsEnabledProperty, myBinding);
            mi2.Command = KeyFrameOption_ClickedCommand;
            mi2.CommandParameter = KeyFramePropertyTypes.EaseOut;
            mi.Items.Add(mi2);

            // Ease InOut
            mi2 = new MenuItem();
            mi2.Name = "EaseInOut";
            mi2.Header = "Ease In&Out";
            //mi2.IsCheckable = true;
            myBinding = new Binding("HasEaseInOut");
            myBinding.Mode = BindingMode.OneWay;
            myBinding.Source = kf;
            mi2.SetBinding(MenuItem.IsCheckedProperty, myBinding);
            myBinding = new Binding("HasHoldValue");
            myBinding.Mode = BindingMode.OneWay;
            myBinding.Converter = new BoolInvertConverter();
            myBinding.Source = kf;
            mi2.SetBinding(MenuItem.IsEnabledProperty, myBinding);
            mi2.Command = KeyFrameOption_ClickedCommand;
            mi2.CommandParameter = KeyFramePropertyTypes.EaseInOut;
            mi.Items.Add(mi2);

            cm.Items.Add(mi);

            if (kf.KeyFrameType == CustomKeyFrameTypes.Point3D)
            {
                // INTERPOLATION
                mi = new MenuItem();
                mi.Header = "Interpolation";

                mi2 = new MenuItem();
                mi2.Header = "Linear:Linear";
                mi2.Name = "LinearLinear";
                //mi2.IsCheckable = true;
                myBinding = new Binding("HasLinearLinear");
                myBinding.Mode = BindingMode.OneWay;
                myBinding.Source = kf;
                mi2.SetBinding(MenuItem.IsCheckedProperty, myBinding);
                mi2.Command = KeyFrameOption_ClickedCommand;
                mi2.CommandParameter = KeyFramePropertyTypes.InterpolationLinearLinear;
                mi.Items.Add(mi2);

                mi2 = new MenuItem();
                mi2.Header = "Linear:Bezier";
                mi2.Name = "LinearBezier";
                //mi2.IsCheckable = true;
                myBinding = new Binding("HasLinearBezier");
                myBinding.Mode = BindingMode.OneWay;
                myBinding.Source = kf;
                mi2.SetBinding(MenuItem.IsCheckedProperty, myBinding);
                mi2.Command = KeyFrameOption_ClickedCommand;
                mi2.CommandParameter = KeyFramePropertyTypes.InterpolationLinearBezier;
                mi.Items.Add(mi2);

                mi2 = new MenuItem();
                mi2.Header = "Bezier:Linear";
                mi2.Name = "BezierLinear";
                //mi2.IsCheckable = true;
                myBinding = new Binding("HasBezierLinear");
                myBinding.Mode = BindingMode.OneWay;
                myBinding.Source = kf;
                mi2.SetBinding(MenuItem.IsCheckedProperty, myBinding);
                mi2.Command = KeyFrameOption_ClickedCommand;
                mi2.CommandParameter = KeyFramePropertyTypes.InterpolationBezierLinear;
                mi.Items.Add(mi2);

                mi2 = new MenuItem();
                mi2.Header = "Bezier:Bezier";
                mi2.Name = "BezierBezier";
                //mi2.IsCheckable = true;
                myBinding = new Binding("HasBezierBezier");
                myBinding.Mode = BindingMode.OneWay;
                myBinding.Source = kf;
                mi2.SetBinding(MenuItem.IsCheckedProperty, myBinding);
                mi2.Command = KeyFrameOption_ClickedCommand;
                mi2.CommandParameter = KeyFramePropertyTypes.InterpolationBezierBezier;
                mi.Items.Add(mi2);

                mi.Items.Add(sep);

                mi2 = new MenuItem();
                mi2.Header = "Auto Bezier";
                mi2.Name = "AutoBezier";
                //mi2.IsCheckable = true;
                myBinding = new Binding("HasAutoBezier");
                myBinding.Mode = BindingMode.OneWay;
                myBinding.Source = kf;
                mi2.SetBinding(MenuItem.IsCheckedProperty, myBinding);
                mi2.Command = KeyFrameOption_ClickedCommand;
                mi2.CommandParameter = KeyFramePropertyTypes.AutoBezier;
                mi.Items.Add(mi2);

                cm.Items.Add(mi);
            }

            cm.Items.Add(new Separator());

            mi = new MenuItem();
            mi.Header = "Toggle Hold";
            mi.Name = "ToggleHold";
            myBinding = new Binding("HasHoldValue");
            myBinding.Mode = BindingMode.OneWay;
            myBinding.Source = kf;
            mi.SetBinding(MenuItem.IsCheckedProperty, myBinding);
            myBinding = new Binding("HasAddElapsedInterpolation");
            myBinding.Mode = BindingMode.OneWay;
            myBinding.Converter = new BoolInvertConverter();
            myBinding.Source = kf;
            mi.Command = KeyFrameOption_ClickedCommand;
            mi.CommandParameter = KeyFramePropertyTypes.Hold;

            cm.Items.Add(mi);

            KeyRectangle.ContextMenu = cm;
        }

        private void EditValueClicked(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            double doubleOrig;
            EditPoint3DDataTemplate pointOrig;
            CustomKeyFrame kf = (CustomKeyFrame)DataContext;
            CustomDoubleKeyFrame kfd;
            CustomPoint3DKeyFrame kfp;

            if (kf.KeyFrameType == CustomKeyFrameTypes.Double)
            {
                kfd = (CustomDoubleKeyFrame)kf;
                doubleOrig = kfd.Value;
                EditDoubleValueDialogBox dlg = new EditDoubleValueDialogBox();
                dlg.DataContext = kf;
                dlg.Owner = Application.Current.MainWindow;
                dlg.ShowDialog();

                if (dlg.DialogResult == true)
                {
                    ServicesFactory.EventService.GetEvent<CompLayerPropertyChangedEvent>().Publish(new CompLayerPropertyChangedEventArgs() { Sender = this, PropertyType = "Value", UpdateCurrentValues = true });
                    if (doubleOrig != kfd.Value)
                    {
                        LfsRecordController.RecordUserAction(new UserActionKeyFrameDoubleValueChanged()
                        {
                            LayerIndex = kf.ParentLayer.LayerIndex,
                            PropertyIndex = kf.ParentProperty.PropertyIndex,
                            KeyFrameIndex = kf.KeyFrameIndex,
                            ValueOffset = kfd.Value - doubleOrig
                        });
                        LfsRecordController.CreateUndoLevel();
                    }
                }
                else
                {
                    // Cancelled
                    kfd.Value = doubleOrig;
                }
            }
            else if (kf.KeyFrameType == CustomKeyFrameTypes.String)
            {

            }
            else
            {
                kfp = (CustomPoint3DKeyFrame)kf;
                pointOrig = new EditPoint3DDataTemplate();
                pointOrig.xValue = kfp.xValue;
                pointOrig.yValue = kfp.yValue;
                pointOrig.zValue = kfp.zValue;
                EditPoint3DValueDialogBox dlg = new EditPoint3DValueDialogBox();
                dlg.DataContext = pointOrig;
                dlg.Owner = Application.Current.MainWindow;
                dlg.ShowDialog();

                if (dlg.DialogResult == true)
                {
                    if (kfp.Value.X != pointOrig.xValue ||
                        kfp.Value.Y != pointOrig.yValue ||
                        kfp.Value.Z != pointOrig.zValue)
                    {
                        LfsRecordController.RecordUserAction(new UserActionKeyFramePoint3DValueChanged()
                        {
                            LayerIndex = kf.ParentLayer.LayerIndex,
                            PropertyIndex = kf.ParentProperty.PropertyIndex,
                            KeyFrameIndex = kf.KeyFrameIndex,
                            ValueOffset = new Vector3D(pointOrig.xValue - kfp.Value.X, pointOrig.yValue - kfp.Value.Y, pointOrig.zValue - kfp.Value.Z)
                        });

                        ((CustomPoint3DKeyFrame)kf).Value = new Point3D(
                            pointOrig.xValue,
                            pointOrig.yValue,
                            pointOrig.zValue
                            );

                        LfsRecordController.CreateUndoLevel();
                    }
                    kf.OnPropertyChanged("ReCacheDummyWithAutoBezierCore");
                    ServicesFactory.EventService.GetEvent<CompLayerPropertyChangedEvent>().Publish(new CompLayerPropertyChangedEventArgs() { Sender = this, PropertyType = "Value", UpdateCurrentValues = true });
                }
            }
        }

        // KeyFrameOption_ClickedCommand
        RelayCommand _keyFrameOption_ClickedCommand;
        public ICommand KeyFrameOption_ClickedCommand
        {
            get
            {
                if (_keyFrameOption_ClickedCommand == null)
                    _keyFrameOption_ClickedCommand = new RelayCommand(param => KeyFrameProperty_ClickedCommandExecute(param),
                                                                      param => KeyFrameProperty_ClickedCommandCanExecute(param));
                return _keyFrameOption_ClickedCommand;
            }
        }
        bool KeyFrameProperty_ClickedCommandCanExecute(object param)
        {
            if (param == null)
                return true;

            CustomKeyFrame kf = (CustomKeyFrame)DataContext;
            KeyFramePropertyTypes kt = (KeyFramePropertyTypes)param;
            if (kt == KeyFramePropertyTypes.AutoBezier)
                return (kf.HasLinearLinear) ? false : true;
            return true;
        }
        void KeyFrameProperty_ClickedCommandExecute(object optionType)
        {
            CustomKeyFrame kf = (CustomKeyFrame)DataContext;
            KeyFramePropertyTypes ot = (KeyFramePropertyTypes)optionType;
            //Debug.WriteLine("Option checked : " + ot);

            CustomKeyFrameOptions oldOptions = kf.Options;
            KeyFrameInterpolationTypes oldIntIn = kf.InterpolationTypeIn;
            KeyFrameInterpolationTypes oldIntOut = kf.InterpolationTypeOut;
            double oldEaseIn = kf.EaseInValue;
            double oldEaseOut = kf.EaseOutValue;

            switch (ot)
            {
                case KeyFramePropertyTypes.NoEase:
                    kf.EaseInValue = CustomDoubleEase.NoEase;
                    kf.EaseOutValue = CustomDoubleEase.NoEase;
                    break;
                case KeyFramePropertyTypes.EaseIn:
                    kf.EaseInValue = CustomDoubleEase.DefaultInValue;
                    kf.EaseOutValue = CustomDoubleEase.NoEase;
                    break;
                case KeyFramePropertyTypes.EaseOut:
                    kf.EaseInValue = CustomDoubleEase.NoEase;
                    kf.EaseOutValue = CustomDoubleEase.DefaultOutValue;
                    break;
                case KeyFramePropertyTypes.EaseInOut:
                    kf.EaseInValue = CustomDoubleEase.DefaultInValue;
                    kf.EaseOutValue = CustomDoubleEase.DefaultOutValue;
                    break;
                case KeyFramePropertyTypes.InterpolationLinearLinear:
                    kf.InterpolationTypeIn = kf.InterpolationTypeOut = KeyFrameInterpolationTypes.Linear;
                    break;
                case KeyFramePropertyTypes.InterpolationLinearBezier:
                    kf.InterpolationTypeIn = KeyFrameInterpolationTypes.Linear;
                    kf.InterpolationTypeOut = KeyFrameInterpolationTypes.Bezier;
                    break;
                case KeyFramePropertyTypes.InterpolationBezierLinear:
                    kf.InterpolationTypeIn = KeyFrameInterpolationTypes.Bezier;
                    kf.InterpolationTypeOut = KeyFrameInterpolationTypes.Linear;
                    break;
                case KeyFramePropertyTypes.InterpolationBezierBezier:
                    kf.InterpolationTypeIn = kf.InterpolationTypeOut = KeyFrameInterpolationTypes.Bezier;
                    break;
                case KeyFramePropertyTypes.AutoBezier:
                    kf.Options ^= CustomKeyFrameOptions.AutoBezier;
                    break;
                case KeyFramePropertyTypes.Hold:
                    kf.Options ^= CustomKeyFrameOptions.Hold;
                    break;
            }

            if (kf.Options != oldOptions)
            {
                LfsRecordController.RecordUserAction(new UserActionKeyFrameOptionChanged()
                {
                    LayerIndex = kf.ParentLayer.LayerIndex,
                    PropertyIndex = kf.ParentProperty.PropertyIndex,
                    KeyFrameIndex = kf.KeyFrameIndex,
                    OldOptions = oldOptions,
                    NewOptions = kf.Options
                });
            }
            if (kf.EaseInValue != oldEaseIn || kf.EaseOutValue != oldEaseOut)
            {
                LfsRecordController.RecordUserAction(new UserActionKeyFrameEaseChanged()
                    {
                        LayerIndex = kf.ParentLayer.LayerIndex,
                        PropertyIndex = kf.ParentProperty.PropertyIndex,
                        KeyFrameIndex = kf.KeyFrameIndex,
                        OldEaseIn = oldEaseIn,
                        NewEaseIn = kf.EaseInValue,
                        OldEaseOut = oldEaseOut,
                        NewEaseOut = kf.EaseOutValue
                    });
            }
            if (kf.InterpolationTypeIn != oldIntIn || kf.InterpolationTypeOut != oldIntOut)
            {
                LfsRecordController.RecordUserAction(new UserActionKeyFrameInterpolationChanged()
                    {
                        LayerIndex = kf.ParentLayer.LayerIndex,
                        PropertyIndex = kf.ParentProperty.PropertyIndex,
                        KeyFrameIndex = kf.KeyFrameIndex,
                        OldIntIn = oldIntIn,
                        NewIntIn = kf.InterpolationTypeIn,
                        OldIntOut = oldIntOut,
                        NewIntOut = kf.InterpolationTypeOut
                    });
            }
            LfsRecordController.CreateUndoLevel();

            ServicesFactory.EventService.GetEvent<CompLayerPropertyChangedEvent>().Publish(new CompLayerPropertyChangedEventArgs() { Sender = this, PropertyType = "KeyFrameOptions", UpdateCurrentValues = true });
        }
    }
}
